package aconcagua.measure

class MeasureBag extends MeasureBehavior {
	private measures;

	public MeasureBag(aMeasuresCollection) {
		super();
		measures = aMeasuresCollection.collect({ it.convertToBaseUnit() });
	}

	public static with(aMeasure, anotherMeasure) {
		/* This message is not due to never use. A MeasureBag is created sending
		 * the messages #+ or #-.
		 * If the two measures are of the same domain, directly it gives back a
		 * canonized measure to the base unit.
		 * If they are of different domains, adds the measures canonized.
		 * This comparison is here and not in Measure so that they are not possible
		 * to be created incorrect bag.
		 * 
		 * DEFINITION: A MeasureBag always has its measures canonized.
		 */

		def measure;

		if(aMeasure.sameDomainAs(anotherMeasure))
			measure = aMeasure.addSameDomainMeasure(anotherMeasure)
		else
			measure = aMeasure.createBagWith(anotherMeasure);
		return measure;
	}

	// accessing
	@Override
	public Object amount() {
		throw new UndefinedMeasureBagAmountException();
	}

	@Override
	public Object measures() {
		return measures;
	}

	@Override
	public Object unit() {
		throw new UndefinedMeasureBagUnitException();;
	}

	@Override
	public Object unitNameOf(Object aUnit) {
		throw new UndefinedMeasureBagUnitException();
	}

	public atSameBaseUnitAs(aUnit) {
		return this.atBaseUnit(aUnit, { unit -> new Measure(0, unit.baseUnit()) });
	}

	public atBaseUnit(aUnit, aClosure) {
		def found = measures.find({ measure -> measure.baseUnit() == aUnit.baseUnit() })
		return (found != null) ? found : aClosure.call(aUnit);
	}

	public numberOfMeasures() {
		return measures.size();
	}

	// arithmetic operations
	@Override
	public Object div(Object anArithmeticObject) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object intdiv(Object anArithmeticObject) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object mod(Object anArithmeticObject) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object multiply(Object anArithmeticObject) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object negate() {
		return this.newBagDoingOnEachMeasure({ measure -> measure.negate() });
	}

	@Override
	public Object plus(Object anArithmeticObject) {
		return anArithmeticObject.addMeasureBag(this);
	}

	@Override
	public Object round(Object aNumber) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object trunc() {
		// TODO Auto-generated method stub
		return null;
	}

	// arithmetic operations - private
	private addMeasure(aMeasure) {
		def sameBaseUnitMeasure = this.atSameBaseUnitAs(aMeasure.unit());
		def newMeasures = measures.findAll({ measure -> measure.baseUnit() != sameBaseUnitMeasure.baseUnit() });
		newMeasures.add(sameBaseUnitMeasure + aMeasure);
		def nonNullMeasures = newMeasures.findAll({ measure -> !measure.isNothing() });

		return (nonNullMeasures.size == 1) ? nonNullMeasures.first() : new MeasureBag(nonNullMeasures);
	}

	private addMeasureBag(aMeasureBag) {
		def Map measuresByUnit = [:];
		measures.each({ measure -> measuresByUnit.put(measure.baseUnit(), measure) });
		aMeasureBag.measures().each({ measure ->
			def measureToAdd = measuresByUnit.get(measure.baseUnit(), 0);
			measuresByUnit.put(measure.baseUnit(), measureToAdd + measure) });
		def nonNullMeasures = measuresByUnit.values().findAll({ measure -> measure!=null && measure.isSomething() });
		if(nonNullMeasures.isEmpty())
			return  0
		else if(nonNullMeasures.size == 1)
			return nonNullMeasures.first()
		else
			return new MeasureBag(nonNullMeasures.inject([],
				{ array, measure -> array.addAll(measure.measures()); array }));
	}

	private newBagDoingOnEachMeasure(aClosure) {
		def newMeasures = measures.collect(aClosure);
		return new MeasureBag(newMeasures);
	}

	// comparing
	@Override
	public boolean equals(anObject) {
		return anObject.equalsMeasureBag(this);
	}

	@Override
	public int hashCode() {
		return measures.inject(0, { hash, measure -> hash + measure.hashCode() });
	}

	@Override
	public boolean equalsMeasure(aMeasure) {
		return measures.every({ it.sameDomainAs(aMeasure) ? it == aMeasure : it.isNothing() });
	}

	@Override
	public boolean equalsMeasureBag(aMeasureBag) {
		return (this - aMeasureBag).isNothing();
	}

	@Override
	public Object convertTo(Object aTargetUnit) {
		// TODO Auto-generated method stub
		return null;
	}

	// testing
	@Override
	public boolean isMeasureBag() {
		return true;
	}
	
	@Override
	public boolean isNothing() {
		return measures.every({ measure -> measure.isNothing() });
	}

}
